feat(harbor): task.yaml/experiment.yaml → Harbor export + coder-eval as a Harbor agent - #166
Merged
Merged
Conversation
…he grader Implements C0/C1.1/C1.4/C2/C3(golden) of the Harbor interop plan (tmp/harborframework.md): `coder-eval export --format harbor <task.yaml> -o <dir>` emits a Harbor task directory whose `tests/test.sh` grades with coder-eval's own criteria via a two-line verifier shim. - `coder_eval.harbor.reward` — translates a graded run's task.json into Harbor's reward.json contract. A missing/unmeasured weighted_score writes no reward file (never coalesced to 0.0), so Harbor's own RewardFileNotFoundError path masks the trial instead of scoring it zero. - `coder_eval.harbor.portability` — classifies all 15 criterion types so an unsupported criterion is refused at export time, not as a mystery low reward at verify time. Registry-derived coverage over the real SuccessCriterion union. - `coder_eval.harbor.packager` — the export writer itself: task.toml, instruction.md, environment/, tests/test.sh, tests/task.yaml, tests/reference/. New dependency: tomli-w. - `coder-eval harbor reward` / `coder-eval export` CLI commands. - CE004 widened to scope harbor/ as a core layer (must not import coder_eval.cli). Verified live against real Harbor 0.22.0 + real docker (not just unit tested): exported tasks scored reward 1.0 (correct solution), 0.5 (wrong content, genuinely measured not masked), and correctly masked with no solution present. Two bugs the live run found and fixed that no unit test caught: test.sh referenced tests/task.yaml as cwd-relative when Harbor uploads the whole tests/ tree to the absolute /tests/; and `set -e` in test.sh was silently discarding every real failing score as if it were unmeasured, since `coder-eval evaluate` exits non-zero on a failed criterion (a measured verdict, not an infra failure). v1 assumes the exported task's Dockerfile is FROM coder-eval-agent:<tag> (confirmed live) so the verifier's `coder-eval` binary is present; the packager warns, non-blocking, when it can't confirm that. Full design, verification log and known follow-up (C1.2/C1.3, solution/ auto-derivation, network:none, docs) recorded in tmp/harborframework.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ng /app
Converting a real in-tree task (hello_date_smoke_test) through the export
pipeline surfaced a live bug in the bare-`docker.image` branch of
`_write_environment`: it defaulted `workdir` to the hardcoded
`DEFAULT_WORKDIR = "/app"` whenever `sandbox.docker.working_dir` was unset,
regardless of the image's actual WORKDIR.
`coder-eval-agent:latest`'s real WORKDIR is `/work`. Harbor's shared verifier
runs `docker exec -w <workdir> <container> ...`, and confirmed live that
`docker exec -w` -- unlike `docker run -w`, which auto-creates the directory
-- hard-fails to chdir into a path that doesn't already exist in the image
("no such file or directory"), surfacing as agent exit 127 with no verifier
output at all. The dockerfile_path branch never had this bug (it appends a
WORKDIR line and the directory exists once Docker builds it); the bare-image
branch had no Dockerfile to fix and no way to introspect the image, so it
just guessed.
Fix: `_inspect_image_workdir()` runs `docker image inspect <image> --format
'{{.Config.WorkingDir}}'` (best-effort, 30s timeout, returns None on any
failure) and the bare-image branch now tries, in order: explicit
`working_dir` -> inspected WORKDIR -> `DEFAULT_WORKDIR`, warning
(non-blocking) only on the last fallback.
Re-verified live after the fix: `harbor run -p <dir> -a oracle` against the
converted hello_date task scored reward: 1.0.
Test hygiene: added an autouse fixture stubbing `_inspect_image_workdir`
across `test_harbor_packager.py` so packager unit tests never depend on
what happens to be cached in the local docker daemon -- a real image named
`byod-custom-image:0.1.0` (this file's own placeholder name), built by an
unrelated docker-integration test elsewhere in the suite, silently flipped
a DEFAULT_WORKDIR assertion the first time this ran locally. New tests
(TestPrebuiltImageWorkdirInspection) cover the inspection helper directly
and its three-way fallback order.
Full narrative in tmp/harborframework.md (gitignored), including a scoping
note for the next piece (coder-eval as Harbor's *agent*, not just its
verifier) -- not started, planning only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the single-task Harbor exporter to experiment.yaml: each resolved (task, variant, replicate[, dataset row]) combination gets its own Harbor directory, since Harbor's task.toml has no variant concept. Reuses resolve_all_tasks (the same pipeline `run -e` uses) for resolution, and skips (rather than silently drops) a variant whose agent/simulation override Harbor's verifier-only export cannot honor, detected precisely via config_lineage so a task's own agent config never false-triggers it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adopts ATIF (Agent Trajectory Interchange Format) as coder-eval's trajectory interchange layer so a Harbor trial can run coder-eval itself as the agent, not just as the grader. - harbor/atif_models.py + atif_emit.py: vendored ATIF schema + a pure EvaluationResult -> Trajectory converter, opt-in via `--format harbor` on `execute`/`run` (writes a trajectory.json sibling to task.json; never changes default output). - harbor/atif_hydrate.py: the reverse direction (Trajectory -> TurnRecords) so `evaluate --format harbor --trajectory <path>` can grade trajectory-dependent criteria against an ATIF file that didn't originate from an in-process run. - harbor/agent.py: CoderEvalAgent(BaseInstalledAgent) — a real Harbor agent that shells out to `coder-eval execute --format harbor` inside the container and lets Harbor discover the resulting trajectory.json via `populate_context_post_run`, matching Harbor's own ClaudeCode agent's exact convention. Verified end-to-end against a real harbor==0.22.0 install: a full export -> docker build -> `harbor run` -> grade round trip produces correct rewards for both a passing and a deliberately failing criterion. - packager.py: bakes a criteria-free agent-phase task.yaml into the image at a fixed path (agent_paths.py) via a Dockerfile COPY line, so CoderEvalAgent always has a task to execute without ever seeing the real success_criteria. - run_command.py: when `--format harbor` writes exactly one trajectory (the shape a CoderEvalAgent invocation always produces), it is also copied to <run_dir>/trajectory.json so a fixed-path caller (Harbor's agent log dir) can find it without knowing coder-eval's internal variant/task/replicate nesting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…plate sources
Several fixes to the Harbor export/agent pipeline, each verified against a
real `harbor==0.22.0` install with real Docker and (where relevant) real
Bedrock credentials:
- CoderEvalAgent now passes --workspace-dir "$(pwd)" to `coder-eval execute`,
and BatchRunConfig/run_batch/run+execute CLIs gained a matching
--workspace-dir flag (Orchestrator already supported this internally for
DockerRunner). Without it, the agent phase wrote to a throwaway tempdir the
verifier phase never looked at, silently scoring every criterion 0.
- packager.py now emits [environment.env]/[verifier.env] in task.toml, derived
from the resolved task's own sandbox.docker.env_passthrough (the same
allowlist DockerRunner already uses) rather than requiring the operator to
hand-edit task.toml. HOME is excluded (that entry only makes sense paired
with DockerRunner's ~/.claude bind-mount). Values are `${VAR:-}` templates
(empty default) so Harbor doesn't hard-require every var to be present.
- environment/Dockerfile is now always written (synthesized from
sandbox.docker.image when no dockerfile_path is set), so the CoderEvalAgent
embed always has somewhere to COPY task.yaml into.
- instruction.md is now a fixed placeholder pointing at environment/task.yaml
and the CoderEvalAgent agent, instead of leaking the real prompt to a
stock Harbor agent that isn't the one this export is built for.
- environment/task.yaml's success_criteria is now a real `[]` instead of a
placeholder criterion, now that TaskDefinition.validate_success_criteria
(the "at least one criterion" check) has been removed.
- TemplateDirSource directories are now copied into
environment/templates/<n>-<name>/ with their path rewritten to the
in-container copy; environment/task.yaml's sandbox block is field-merged
(driver forced to tempdir) instead of being replaced wholesale, so
python.env_packages/limits/etc. survive.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Installs harbor==0.22.0 alongside coder-eval in the same venv, builds the coder-eval-agent and byod-custom-image Docker images, then exports and runs four scenarios via `hb run -a coder_eval.harbor.agent:CoderEvalAgent`: baseline (plain docker driver, default image), llm_judge (real judge call inside the verifier phase), docker_custom_image (reuses tasks/byod_smoke_test.yaml's dockerfile_path + custom image), and template_sources (TemplateDirSource copy-in). Each scenario asserts reward.json == 1.0, a real ATIF trajectory.json, and both agent- and verifier-side task.json. Verified locally end-to-end against real Docker + real Bedrock credentials before committing (all four scenarios: reward 1.0, no exceptions). Triggered on workflow_dispatch/schedule/push-to-main only (not pull_request) so it's informational rather than a required PR check for now — it depends on live model calls and Docker builds, and Harbor's own release cadence is independent of this repo's. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
akshaylive
requested review from
CarlesUIPath,
bai-uipath,
tmatup and
uipreliga
as code owners
September 11, 2026 18:06
…kage `harbor` is intentionally not a project dependency -- coder_eval.harbor.agent only ever runs inside a Harbor trial container, where it's already present (see the module's own ImportError message). CI's Quality Gate installs the codex/litellm extras, which cleared those pre-existing pyright errors, so these 3 harbor.* import errors were the only real failures blocking PR #166 -- scoped `# pyright: ignore[reportMissingImports]` on the same 3 lines, matching the existing pattern for openai_codex's optional import in codex_agent.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two more CI-only failures found running PR #166's full Quality Gate/Windows Smoke Test jobs (masked previously by the typecheck failure that aborted before the test step ran): - test_execute_format_harbor.py / test_evaluate_format_harbor.py assert a plain substring ("Unsupported --format", "--trajectory") against a raised Click/Typer error's rendered output. CI renders that error box with color, which splits "--format"/"--trajectory" across ANSI escape sequences and silently breaks a naive substring check -- reproduced locally with FORCE_COLOR=1. Fixed with click.unstyle(result.output) before asserting. - Two harbor packager tests assert the exported test.sh's chmod executable bit, which is meaningless on NTFS (Windows Smoke Test runs on Windows). Skipped/guarded the same way test_sandbox.py already does for other POSIX-only behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes surfaced by /coder-eval-code-review-full over two iterations: - Refuse a task with empty success_criteria under `run`/`evaluate` (grade=True) -- it previously loaded and would silently score FinalStatus.SUCCESS at weighted_score 0.0 since validate_success_criteria was removed to let the criteria-free Harbor agent-phase task.yaml round-trip through `execute`. `execute` (grade=False) still accepts it. Scoped to the post-`--resume` task set so an already-finalized row is never blocked. - Refuse a path-traversal-shaped variant_id/task_id/row_id in experiment export (`_out_subdir`) instead of silently writing outside `-o`'s output directory. - Convert `--workspace-dir` misuse (docker driver, >1 task) from an unhandled ValueError/traceback into a clean typer.BadParameter. - Escalate a missing `template_sources` directory during export from a warning to a hard failure -- the agent-phase task.yaml still references it, so a silently-skipped copy shipped an export with no starter code. - Add a `harbor` optional extra (pinned harbor==0.22.0) so harbor/agent.py type-checks against real types instead of scoped pyright ignores; wire it into the two CI jobs that run pyright and simplify harbor-e2e.yml's install step to use it. - New test coverage: harbor/agent.py's run()/populate_context_post_run (previously zero coverage), the empty-criteria and workspace_dir guards, the path-traversal refusal, and the template-source warn vs. hard-fail branches. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…utput CI renders Click's error box with color, splitting "--workspace-dir" across escape sequences and breaking the new test_workspace_dir_with_docker_driver_is_a_clean_cli_error test that raw-substring-matched it. Same fix as the existing --format/--trajectory tests: assert against click.unstyle(result.output) instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collaborator
|
Let's see if they like it... i thought that the easiest approach would have been to simplify the tasks, remove some of the criteria while move others to the testing scripts. If some tasks are not possible to score in Harbor, we shouldn't just give them out. |
uipreliga
self-requested a review
September 11, 2026 23:21
…rops)
Closes out the reviewer's Top-5 priority actions from the full 8-axis
review of this branch, plus the two CodeQL findings:
- Restore the empty-`success_criteria` refusal at the layers `run
--to_run`'s guard didn't cover: `regrade_in_place` (shared by `evaluate
<run_dir>` and `run --resume`'s `to_grade` set), the orchestrator-direct
`evaluate` branch, and the resume `to_grade` partition itself. Without
this, a criteria-less task graded SUCCESS at weighted_score 0.0 and
exited 0.
- Widen `write_reward`'s unmeasured-row guard to also refuse
`final_status.category == "error"`, since a grading-time infra crash
(JudgeInfrastructureError/CheckerMisuseError/ReferenceTamperedError)
finalizes ERROR with weighted_score 0.0, not None -- publishing a crash
to Harbor as a measured zero.
- Read `result_status`/`duration_ms` back out of ATIF's ToolCall/
ObservationResult `extra` in atif_hydrate, so `command_executed
(require_success: true)` no longer silently scores 0.0 on the
`evaluate --format harbor` path for a command that succeeded.
- Replace the two hand-picked export payloads: carry `run_limits` (both
agent-phase and verifier task.yaml) and `checker_context` (verifier)
through instead of dropping them, and refuse exporting a `dataset:`-
backed task (unexpanded `${row.*}` placeholders) or an enabled
`simulation:` block.
- `_unhonorable_override_reason` no longer skips agent-override variants:
CoderEvalAgent (C1.2) now carries `task.agent` verbatim and executes it,
so `export -e experiment.yaml` exports one directory per variant instead
of exiting with zero.
- shlex.quote the task-YAML-controlled `working_dir` interpolated into the
generated Harbor verifier `tests/test.sh` (command injection via a
crafted `sandbox.docker.working_dir`).
- Drop symlinks (never dereference) when copying template/reference trees
into an export, matching every other task-authored-tree copy in `src/`;
wrap the reference copy's OSError as TaskNotExportableError.
- `--` before the task-controlled image name in `docker image inspect`.
- Fix the SubagentTrajectoryRef vendored ATIF model to match harbor==0.22.0
exactly (verified against the installed package): all four fields
optional, `session_id` added -- the old required-`trajectory_id` shape
rejected two of harbor's own valid document forms.
- `evaluate --format harbor`'s write-back now checks `target.mode is
RUN_DIR`, not just `prior is not None`: a synthetic prior seeded from
`--trajectory` on the WORK_DIR shape was planting a spurious task.json
into the Harbor-synced work dir.
- Narrow the DIRECT_WRITE stale-artifact warning suppression for
`workspace_dir` to `IN_CONTAINER_ENV` (CE056) instead of the field
itself: `--workspace-dir` is now also a host-reachable CLI flag, where
the named directory persists across runs exactly like DIRECT_WRITE's
own target.
- Remove an unnecessary `del environment` (CodeQL) and fix a test's
dual import style (`import X.Y` + `from X.Y import Z`, CodeQL) for the
same module.
Regenerated the golden export fixture for the `run_limits` addition.
Full suite green (5706 passed), ruff/pyright/lint clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets the same per-task agent image double as a Harbor agent image (coder_eval.harbor.agent:CoderEvalAgent, a harbor.agents.installed.base.BaseInstalledAgent subclass) without a separate build, matching the harbor extra already wired into both pyright CI jobs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Harbor-interop layer for coder-eval:
coder-eval export --format harbor): converts a coder-evaltask.yaml(optionally ×experiment.yamlvariants) into a Harbor task directory, using coder-eval's own criteria as the grader.CoderEvalAgent,-a coder_eval.harbor.agent:CoderEvalAgent): runscoder-eval execute --format harborinside a Harbor trial container, in-place at the container's ownWORKDIR(--workspace-dir), and hands Harbor back a real ATIFtrajectory.json.harbor==0.22.0install, real Docker, and real Bedrock credentials:[environment.env]/[verifier.env]auto-derived fromsandbox.docker.env_passthrough(${VAR:-}templates so Harbor doesn't hard-require every credential var), an always-writtenenvironment/Dockerfile, a fixed placeholderinstruction.md,template_sourcesdirectories copied into the export with rewritten paths, andTaskDefinitionno longer requiring a placeholder success criterion..github/workflows/harbor-e2e.yml) that exports and runs 4 real scenarios (plain docker driver, llm_judge, custom BYOD image, template_sources) end-to-end and assertsreward.json == 1.0+trajectory.json+task.jsonpresence.Test plan
make format && make check && make lintcleanmake typecheckat the known 9-error baseline (pre-existing optional-extra imports, unchanged)make test— 5069 passed, same 10 pre-existing unrelatedlitellm-extra failuresharbor-e2e.ymltriggered viaworkflow_dispatchon this PR's branch to confirm it passes in CI too (not triggered bypull_requestitself — see the workflow's own comment)🤖 Generated with Claude Code